/**
 * 渲染各类型聊天记录
 */
import {
  RedEnvelopeOutlined,
  GlobalOutlined,
  ReloadOutlined,
  LinkOutlined,
  DollarOutlined,
} from '@ant-design/icons'
import { truthy } from '@gnlab/utils'
import { useBoolean, useRefValue } from '@gnlab/utils/env-react'
import { Typography, message, Image } from 'antd'
import { ReactComponent as FileIcon } from '@/desktop/assets/file.svg'
import { ReactComponent as MpIcon } from '@/desktop/assets/mini-program.svg'
import { ReactComponent as VideonumIcon } from '@/desktop/assets/videonum.svg'
import { Video } from '@/desktop/components'
import Link from '@/desktop/components/link'
import formatAssetUrl from '@/desktop/logics/format-asset-url'
import { editorSubject } from '@/desktop/views/chat/editor'
import { formatFileSize, useStreams } from '@/lib/utils'
import services from '@/services'
import { API } from '@/services/common/api'
import { WordType, wordTitleMap, type Word, type TextWord } from '@/services/common/definition'
import {
  ChatRecordType as Type,
  type ChatRecordMap,
  type TextRecord,
} from '@/services/contact-chat'
import { ContactType, makeContactId } from '@/services/contacts'
import {
  RunMode as RobotMode,
  Scope as RobotScope,
  type AIChatMaySuccess,
} from '@/services/features/robot'
import { isURL } from '@/services/utils'
import { formatTextContent, calcuteMediaStyle, useFold, formatChatTime } from '../utils'
import { modals } from './modals'
import { fileStyle } from './renderer-styles'
import { type RecordContact } from '.'
import './index.module.less' // 有部分公共样式是定义在 index 里
import './renderers.module.less'

export type Renderer<T extends Type> = (props: RendererProps<T>) => JSX.Element

interface RendererProps<T extends Type> {
  record: ChatRecordMap[T]
  contact: RecordContact
  readonly: boolean
}

export const renderers: { [T in Type]: Renderer<T> } = {
  [Type.ParseFailed]: RenderParseFailed,
  [Type.Text]: RenderText,
  [Type.Image]: RenderImage,
  [Type.Video]: RenderVideo,
  [Type.Audio]: RenderAudio,
  [Type.File]: RenderFile,
  [Type.Emotion]: RenderEmotion,
  [Type.LinkCard]: RenderLinkCard,
  [Type.PersonCard]: RenderPersonCard,
  [Type.MiniProgram]: RenderMiniProgram,
  [Type.Videonum]: RenderVideonum,
  [Type.Position]: RenderPosition,
  [Type.RedPocket]: RenderRedPocket,
  [Type.TransferMoney]: RenderTransferMoney,
  [Type.GroupAdd]: RenderGroupAdd,
  [Type.GroupRemove]: RenderGroupRemove,
  [Type.ContactNotify]: RenderContactNotify,
  [Type.Call]: RenderCall,
}

// 不需要显示 info 等，完全由 renderer 自己渲染所有内容的类型
export const bareRecordTypes = new Set([
  Type.GroupAdd,
  Type.GroupRemove,
  Type.ContactNotify,
  Type.Call,
])

// ====================================================================

function RenderParseFailed({ record }: RendererProps<Type.ParseFailed>) {
  let content: string
  if (truthy(record.content.appinfo)) content = `[不支持的应用消息] ${record.content.appinfo}`
  else if (record.content.type !== undefined) content = `[不支持的消息类型] ${record.content.type}`
  else content = '[解析失败]'
  return <div styleName="box">{content}</div>
}

function RenderText(props: RendererProps<Type.Text>) {
  const {
    record: {
      content: { text },
      isSend,
    },
    readonly,
  } = props
  return <>
    <div styleName="record-text box">{formatTextContent(text)}</div>
    {!isSend && !readonly ? <RobotReply {...props} /> : null}
  </>
}

function RobotReply(props: RendererProps<Type.Text>) {
  const { record, contact: recordContact } = props
  const { id: recordId, isSend } = record

  const [configs] = useStreams(services.features.robot.configs$)
  const config = configs.get(recordContact.ownerWxid)

  // undefined: 未生成；null 生成中；Word[] 取得结果
  const [reply, setReply] = useState<
    undefined | null | AIChatMaySuccess<{ messages: Word[]; by?: string }>
  >(undefined)

  // 返回只包含文本的 reply 内容
  function getTextReply() {
    return reply && reply.success
      ? reply.data.messages
          .filter((v): v is TextWord => v.type === WordType.Text)
          .map(v => v.text)
          .join('\n')
      : ''
  }

  async function generate() {
    if (!config || reply === null) return
    setReply(null)

    // 收集最近 10 条聊天记录，来总结场景
    const history = services.chat.history.value?.items ?? []
    let index = history.indexOf(record)
    const relatedRecords: TextRecord[] = []
    while (index >= 0 && relatedRecords.length < 10) {
      const record = history[index]
      if (record && record.type === Type.Text) relatedRecords.unshift(record)
      index--
    }
    const res = await services.features.robot.getReply(
      config.profileId,
      relatedRecords.map(v => ({ isReceive: !v.isSend, text: v.content.text })),
    )
    if (!res.success) void message.warning('获取机器人回复失败: ' + res.error.message)
    setReply(res)
  }
  async function send() {
    if (reply?.success === true) {
      await services.resources.wordsLibrary.send(reply.data.messages)
    }
  }

  // 全自动时机器人由后台运行
  const enableContactTypes = new Map([
    [ContactType.external, config?.scope.includes(RobotScope.客户) ?? false],
    [ContactType.internal, config?.scope.includes(RobotScope.员工) ?? false],
    [ContactType.group, config?.scope.includes(RobotScope.群) ?? false],
  ])
  const robotUsable =
    config !== undefined &&
    config.runMode !== RobotMode.全自动 &&
    recordContact.type !== undefined &&
    enableContactTypes.get(recordContact.type) === true

  // 半自动模式自动生成回复
  const canAutoGetReply =
    robotUsable &&
    (recordContact.type !== ContactType.group ||
      !!record.content.atList.find(v => v.userId === recordContact.ownerWxid)) // 对于群聊，仅被 @ 时触发自动生成回复的行为
  const runMode = config?.runMode
  const generateRef = useRefValue(generate)
  useEffect(() => {
    if (!canAutoGetReply || runMode !== RobotMode.半自动 || recordContact.userId === undefined)
      return

    const contactId = makeContactId(recordContact.ownerWxid, recordContact.userId)
    const contact = services.contacts.getById(contactId)
    if (!contact) return

    let cancelled = false
    const timeoutId = setTimeout(() => {
      if (recordId !== contact.latestChat?.id || cancelled) return
      void generateRef.current()
    }, 1000)
    return () => {
      clearTimeout(timeoutId)
      cancelled = true
    }
  }, [canAutoGetReply, isSend, recordId, recordContact, runMode])

  if (!robotUsable) return null

  return <div className="chat-record-extra-info">
    <span>机器人回复：</span>
    {reply === undefined ? <a onClick={generate}>生成</a> : null}
    {reply === null ? <span>生成中...</span> : null}
    {reply?.success === false ? (
      <>
        <span>生成失败：{reply.error.message}...，</span>
        <a onClick={generate}>重试</a>
      </>
    ) : null}
    {reply?.success === true ? (
      <>
        <div>
          {reply.data.messages.map((word, index) => <div key={index}>
            {word.type === WordType.Text
              ? word.text
              : word.type === WordType.LinkCard
              ? `${word.title} ${word.url}`
              : `[${wordTitleMap[word.type]}]`}
          </div>)}
        </div>
        <a onClick={async () => send()} style={{ marginRight: '.5em' }}>
          发送
        </a>
        <a onClick={() => editorSubject.next(getTextReply())} style={{ marginRight: '.5em' }}>
          编辑
        </a>
        <a onClick={generate}>重新生成</a>
      </>
    ) : null}
  </div>
}

function RenderImage(props: RendererProps<Type.Image>) {
    const { record: { id, content: { url, qrcode = '', height = 0, width = 0 } } } = props
    const isEmpty = isEmptyImage(url)
    const [loading, , loaded] = useBoolean(true)
    return <>
        {isEmpty
            ? <div styleName="record-image-refresh" onClick={() => void services.chat.reloadImage(id)}>
                <ReloadOutlined />
                <div>图片加载失败，点此刷新</div>
            </div>
            : <Link href={url} styleName="record-image">
                <img
                    src={formatAssetUrl(url)}
                    onLoad={loaded}
                    style={calcuteMediaStyle({ loading, width, height, maxWidth: 400, maxHeight: 300 })} />
            </Link>
        }
        {qrcode ? <RenderImageQRCode qrcode={qrcode} /> : null}
    </>
}
function isEmptyImage(url: string) {
  return !url || !url.length || !!/^[a-zA-Z]:\/\//.exec(url) || !!/^[a-zA-Z]:\\/.exec(url)
}
function RenderImageQRCode(props: { qrcode: string }) {
  const { qrcode } = props
  const { showingText, fold, switchFold } = useFold(qrcode, 50)
  return <div className="chat-record-extra-info">
    <span>二维码识别：</span>
    {isURL(qrcode) ? (
      <Link styleName="record-image-qrcode-link" href={qrcode}>
        {showingText}
      </Link>
    ) : (
      showingText
    )}
    {fold !== null ? (
      <a styleName="fold" onClick={switchFold}>
        {fold ? '展开' : '收起'}
      </a>
    ) : null}
  </div>
}

function RenderVideo(props: RendererProps<Type.Video>) {
  const {
    record: {
      content: { url, width, height },
    },
  } = props
  const [loading, , loaded] = useBoolean(true)
  const style = calcuteMediaStyle({ loading, width, height, maxWidth: 400, maxHeight: 300 })
  const src = formatAssetUrl(url)
  return <Video src={src} style={style} onLoadedData={loaded} onError={loaded} />
}

function RenderAudio(props: { record: ChatRecordMap[Type.Audio] }) {
  const {
    record: {
      content: { url, text },
    },
  } = props
  const { showingText, fold, switchFold } = useFold(text ?? '', 50)
  return <>
    <audio styleName="record-audio" controls src={formatAssetUrl(url)}>
      当前浏览器无法播放此音频
    </audio>
    {showingText ? (
      <div className="chat-record-extra-info">
        {showingText}
        {fold !== null ? (
          <a styleName="fold" onClick={switchFold}>
            {fold ? '展开' : '收起'}
          </a>
        ) : null}
      </div>
    ) : null}
  </>
}

function RenderFile(props: RendererProps<Type.File>) {
  const {
    record: {
      content: { name, url, size },
    },
  } = props

  if (size > 1024 * 1024 * 1024) {
    return <div styleName="box">[文件体积过大，请至手机APP上查看]</div>
  }
  return <div css={fileStyle}>
    <aside>
      <div className="name">{name}</div>
      <footer>
        {size ? <span className="size">{formatFileSize(size)}</span> : null}
        <a href={formatAssetUrl(url)} download={name}>
          下载
        </a>
      </footer>
    </aside>
    <FileIcon />
  </div>
}

function RenderEmotion(props: RendererProps<Type.Emotion>) {
  const {
    record: {
      content: { url },
    },
  } = props
  const [loading, , loaded] = useBoolean(true)
  return <img
    src={formatAssetUrl(url)}
    style={calcuteMediaStyle({ loading, width: 0, height: 0, maxWidth: 160, maxHeight: 160 })}
    onLoad={loaded}
    onError={loaded}
  />
}

function RenderLinkCard(props: RendererProps<Type.LinkCard>) {
  const {
    record: {
      content: { url, title, image, desc },
    },
    contact: { ownerWxid }
  } = props

  const isGroupInvite = title.startsWith('邀请你加入群聊')

  async function accept() {
    const res = await API('/Room/AcceptIntoRoom', { data: { wxid: ownerWxid, InviteUrl: url } })
    if (res.success) void message.success('已同意进群')
    else void message.error(res.error || '进群失败')
  }

  return <div>
    <Link href={url} styleName="record-link-card">
      <Typography.Paragraph styleName="title" ellipsis={{ rows: 2 }}>
        [链接] {title}
      </Typography.Paragraph>
      {desc || image ? (
        <main>
          <Typography.Paragraph styleName="describe" ellipsis={{ rows: 3 }}>
            {desc}
          </Typography.Paragraph>
          <div styleName="thumb">{image ? <img src={image} /> : <LinkOutlined />}</div>
        </main>
      ) : null}
    </Link>
    {isGroupInvite ? <a styleName="record-link-card-accept" onClick={accept}>同意进群</a> : null}
  </div>
}

function RenderPersonCard(props: RendererProps<Type.PersonCard>) {
  const { record, contact, readonly } = props
  const { content } = record

  function triggerApply() {
    if (!truthy(contact.userId)) return
    const senderWxid = record.isSend
      ? contact.ownerWxid
      : contact.type === ContactType.group
      ? record.groupSenderWxid
      : contact.userId
    modals.applyByPersonCard.next({
      senderWxid,
      personCardUserId: content.userId,
    })
  }

  return <div styleName="record-person-card">
    <main>
      <img src={content.avatar} />
      <div styleName="name">{content.nickname}</div>
    </main>
    <footer>
      <span>名片</span>
      {!readonly ? <a onClick={triggerApply}>添加好友</a> : null}
    </footer>
  </div>
}

function RenderMiniProgram(props: RendererProps<Type.MiniProgram>) {
  const {
    record: { content },
  } = props
  return <div styleName="record-miniprogram">
    <main>
      <header>
        <img styleName="logo" src={content.appicon} referrerPolicy="no-referrer" />
        <span>{content.appname}</span>
      </header>
      <div styleName="title">{content.title || content.appname}</div>
      <div styleName="cover" style={{ backgroundImage: `url(${content.cover_path})` }}></div>
      <div styleName="type-name">
        <MpIcon />
        小程序
      </div>
    </main>
    <footer>QunTool 暂不支持打开小程序，请至手机上查看</footer>
  </div>
}

function RenderVideonum(props: RendererProps<Type.Videonum>) {
  const {
    record: { content },
  } = props
  return <div styleName="record-videonum">
    <main>
      <div styleName="image" style={{ backgroundImage: `url(${content.cover_url})` }}></div>
      <div styleName="info">
        <img styleName="logo" src={content.avatar} />
        <Typography.Paragraph ellipsis={{ rows: 2 }}>{content.desc}</Typography.Paragraph>
      </div>
      <div styleName="type-name">
        <VideonumIcon />
        视频号
      </div>
    </main>
    <footer>QunTool 暂不支持打开视频号，请至手机上查看</footer>
  </div>
}

function RenderPosition({ record }: RendererProps<Type.Position>) {
  return <div styleName="record-position">
    <main>
      <GlobalOutlined />
      <div styleName="info">
        <div styleName="title">{record.content.title}</div>
        <div styleName="address">{record.content.address}</div>
      </div>
    </main>
    <footer>QunTool 暂不支持打开位置信息，请至手机上查看</footer>
  </div>
}

function RenderRedPocket({ record }: RendererProps<Type.RedPocket>) {
  return <div styleName="record-redpocket">
    <main>
      <div styleName="content">
        <RedEnvelopeOutlined />
        <div>
          <div styleName="remark">{record.content.remark}</div>
          <div styleName="money">￥{(record.content.money / 100).toFixed(2)}</div>
        </div>
      </div>
      <footer>微信红包</footer>
    </main>
    <footer>QunTool 暂不支持打开红包，请至手机上查看</footer>
  </div>
}

function RenderTransferMoney({ record }: RendererProps<Type.TransferMoney>) {
  return <div styleName="record-transfer-money">
    <main>
      <div styleName="content">
        <DollarOutlined />
        <div>{record.content.isReceived ? '已接收转账' : '向你发起了一笔转账'}</div>
      </div>
      <footer>
        <div>微信转账</div>
        <div>企业</div>
      </footer>
    </main>
    <footer>QunTool 暂不支持处理转账，请至手机上查看</footer>
  </div>
}

function RenderGroupAdd(props: RendererProps<Type.GroupAdd>) {
  const { record } = props
  return <div styleName="notice-record">
    <div styleName="time">{formatChatTime(record.timestamp)}</div>
    <div>
      {record.content.opUserName}邀请 {record.content.members.map(v => v.name).join('、')} 加入群聊
    </div>
  </div>
}

function RenderGroupRemove(props: RendererProps<Type.GroupRemove>) {
  const { record } = props
  return <div styleName="notice-record">
    <div styleName="time">{formatChatTime(record.timestamp)}</div>
    <div>
      {record.content.opUserName}将{record.content.members.map(v => v.name).join('、')}移出了群聊
    </div>
  </div>
}

function RenderContactNotify(props: RendererProps<Type.ContactNotify>) {
  return <div styleName="notice-record">{props.record.content.content}</div>
}

function RenderCall() {
  return <div styleName="notice-record">收到语音视频聊天</div>
}
